package sia.tp1;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import sia.tp1.genotype.Genotype;

public class GeneticAlgorithm {
	
	static SimulationParams params;

	/**
	 * @param args
	 * @throws Exception 
	 * @throws Exception 
	 */
	public static void main( String[] args ) throws Exception {
		
		List<Genotype> population = null;
		List<Genotype> parents = null;
		List<Genotype> offspring = null;
		Integer currGeneration = 0;
		double bestAptitude = 0;
		double lastAptitude = 0;
		
		params = new SimulationParams(args);
		
		System.out.println("Start: " + new Date());
		
		population = initializePopulation(params.initialPop);
		
		while( currGeneration < params.generationCount ) {
			parents = selection( population );
			offspring = reproduction( parents );
			
			//Combino la poblacion con los hijos y reemplazo
			population.addAll( offspring );
			population = replacement( population );
			
			currGeneration++;
			
			for( Genotype pivot : population ) {
				if( pivot.getAptitude() > bestAptitude )
					bestAptitude = pivot.getAptitude();
			}
	
			if( bestAptitude > lastAptitude ) {
				//Cdo encontre una solucion, la imprimo, y dejo de imprimir
				//las aptitudes, pero sigo segun indique generationCount
				//a ver si encuentra alguna mas minima, optima;
				System.out.println("=========================================");
				System.out.println(bestAptitude + (bestAptitude >= Problem.solution ? " <- Solution!" : ""));
				Collections.sort(population);
				System.out.println(population.get(0));
				System.out.println("NodesCount = " + population.get(0).getNodesCount());
				System.out.println("Height = " + population.get(0).getTree().getHeight() + "\n");
			}
			lastAptitude = bestAptitude;	
		}
		
		System.out.println("End: " + new Date());
	}
	
	private static List<Genotype> initializePopulation( int size ) throws Exception {
		List<Genotype> population = new ArrayList<Genotype>();
		
		Genotype.setExplodeP(params.explodeP);
		
		for( int i=0 ; i<size; i++ ) {
			population.add( new Genotype(params.maxHeight) );
		}
			
		return population;
	}

	private static List<Genotype> selection( List<Genotype> population ) throws Exception {
		
		List<Genotype> selected = new ArrayList<Genotype>();
		
		switch( params.selectionMethod ){
			case elite:
				selected = eliteSelection(population, params.selectionCount);
				break;
			case roulette:
				selected = rouletteSelection(population, params.selectionCount);
				break;
			case universal:
				selected = universalSelection(population, params.selectionCount);
				break;
			case eliteRoulette:
				selected = eliteSelection(population, (int) Math.floor(params.selectionCount/2));
				selected.addAll(rouletteSelection(population, (int) Math.ceil(params.selectionCount/2)));
				break;
			case eliteUniversal:
				selected = eliteSelection(population, (int) Math.floor(params.selectionCount/2));
				selected.addAll(universalSelection(population, (int) Math.ceil(params.selectionCount/2)));
				break;
		}
		
		return selected;
	}
	
	private static List<Genotype> replacement(List<Genotype> population) throws Exception {

		List<Genotype> selected = new ArrayList<Genotype>();
		
		switch( params.replaceMethod ){
			case elite:
				selected = eliteSelection(population, params.initialPop);
				break;
			case roulette:
				selected = rouletteSelection(population, params.initialPop);
				break;
			case universal:
				selected = universalSelection(population, params.initialPop);
				break;
			case eliteRoulette:
				selected = eliteSelection(population, (int) Math.floor(params.initialPop/2));
				selected.addAll(rouletteSelection(population, (int) Math.ceil(params.initialPop/2)));
				break;
			case eliteUniversal:
				selected = eliteSelection(population, (int) Math.floor(params.initialPop/2));
				selected.addAll(universalSelection(population, (int) Math.ceil(params.initialPop/2)));
				break;
		}
		
		return selected;
	}
	
	private static List<Genotype> rouletteSelection( List<Genotype> population, int size ) throws Exception {
		
		List<Double> nRandoms = new ArrayList<Double>();
		List<Genotype> selected = new ArrayList<Genotype>();
		
		double aptitudeTotal = 0;
		double currAptitude;
		
		//Genero n randoms
		for( int i=0 ; i<size ; i++ ) {
			nRandoms.add( Math.random() );
		}
		
		//Ordeno los randoms
		Collections.sort( nRandoms );

		//Pondero cada genotype y le asigno su aptitud
		for( Genotype genotype : population ) {
			currAptitude = Problem.aptitude( genotype );
			genotype.setAptitude( currAptitude );
			aptitudeTotal += currAptitude;
		}
		
		//Itero sobre los genotypes y genero las aptitudes Acumuladas
		currAptitude = 0;
		for( Genotype genotype : population ) {
			currAptitude += genotype.getAptitude()/aptitudeTotal;
			genotype.setAcumAptitude( currAptitude );
		}
		
		//Itero sobre los n randoms
		for( Double currRandom : nRandoms ) {
			//Por cada random me fijo que genotype cae
			for ( Genotype genotype : population ) {
				if( genotype.getAcumAptitude() >= currRandom ) {
					selected.add( genotype );
					break;
				}
			}
		}

		return selected;
	}
	
	private static List<Genotype> universalSelection(List<Genotype> population, int size) throws Exception {
		
		List<Genotype> selected = new ArrayList<Genotype>();
		List<Double> nrjs = new ArrayList<Double>();
		
		double aptitudeTotal = 0;
		double currAptitude;
		
		//Genero 1 randoms
		double r = Math.random();

		//Pondero cada genotype y le asigno su aptitud
		for( Genotype genotype : population ) {
			currAptitude = Problem.aptitude( genotype );
			genotype.setAptitude( currAptitude );
			aptitudeTotal += currAptitude;
		}
		
		//Itero sobre los genotypes y genero las aptitudes Acumuladas
		currAptitude = 0;
		for( Genotype genotype : population ) {
			currAptitude += genotype.getAptitude()/aptitudeTotal;
			genotype.setAcumAptitude( currAptitude );
		}
		
		// r_j = (r+j-1)/k
		for(int j=1 ; j<=size ; j++) {
			nrjs.add( (r+j-1)/size );
		}
		
		//Itero sobre los r_j
		for( Double r_j : nrjs ) {
			//Por cada r_j me fijo que genotype cae
			for ( Genotype genotype : population ) {
				if( genotype.getAcumAptitude() >= r_j ) {
					selected.add( genotype );
					break;
				}
			}
		}

		return selected;
	}

	private static List<Genotype> eliteSelection(List<Genotype> population, int size) throws Exception {
		
		List<Genotype> selected = new ArrayList<Genotype>();
		double currAptitude;
		
		//Pondero cada genotype y le asigno su aptitud
		for( Genotype genotype : population ) {
			currAptitude = Problem.aptitude( genotype );
			genotype.setAptitude( currAptitude );
		}
		
		//Sorteo por aptitud de mejor a peor
		Collections.sort(population);

		for(int i=0 ; i<size ; i++)
			selected.add(population.get(i));
		
		return selected;
	}

	private static List<Genotype> reproduction( List<Genotype> parents ) {
		List<Genotype> sons = new ArrayList<Genotype>();

		//Primero hacemos el cruce
		for( int i=0 ; i<parents.size()-1 ; i++ ) {
				sons.addAll( parents.get(i).cross( parents.get(++i)) );
		}
		
		//Despues la mutacion
		for( int j=0 ; j<sons.size() ; j++ ) {
				sons.get(j).mutate(params.mutationP);				
		}
		
		return sons;
	}
}
	